/**
 * @class 小行星带实体
 * @author DuskyHuang 圣子
*/

import { _decorator, Node, Vec3, Component, } from 'cc';
import { core, utils } from 'cfw';
import { LAYER_MAIN_3D, Res, SceneGraph } from 'const';
import { attenuate } from 'algorithms';
import { createNodeAt, setLabelStyle, } from 'helpers';
import { CelestialLabel } from '../ui/items/celestial-label';
import { ui, UIProxy, visit } from 'proxies';
import { Asteroid } from './asteroid';
import { ICelestial } from '../celestial';
const { ccclass } = _decorator;

@ccclass('cocosmos.Belts')
export class Belts extends Component implements ICelestial {

    public get configs(): Record<string, beltConfig> { return this._configs; }
    public readonly cat = 'belts';
    public readonly nearest: pow10 = -12;
    public readonly suggest: pow10 = -11.2;
    public get panorama(): pow10 { return -3.5 }
    public get belong(): ICelestial { return this._belong; }

    public placeInSolar(): void {
        this.node.setPosition(Vec3.ZERO);
        this._placeInPlanetary = false;
    }

    public placeInPlanetary(name: string): void {
        const [, dist] = this._belts.find(([belt,]) => belt.name === name);
        this.node.setPosition(dist, 0, 0);
        this._placeInPlanetary = true;
    }

    private set _activeBelts(arg: boolean) {
        this._belts.forEach(([belt,]) => belt.active = arg);
    }

    protected update(): void {
        const { inPlanetary, center } = visit;
        const inactive = inPlanetary && center !== this;
        this._activeBelts = !inactive;
        if (inactive) return;
        for (let batch of this._batches) {
            if (!batch.instances) {
                const { config, belt } = batch;
                const template = core.res.getPrefab(Prefabs.ASTEROID);
                const viewer = utils.findCamera(COSMOS, MAIN);
                batch.activating = true;
                batch.instances = [];
                for (let i = 0; i < NUM_IN_BATCH; i++) {
                    const asteroid = createNodeAt('Asteroid', LAYER_MAIN_3D, belt, template).getComponent(Asteroid);
                    asteroid.loadConfig(config, viewer, null, this._sun);
                    batch.instances.push(asteroid);
                }
                break;
            } else if (!batch.activating) {
                batch.activating = true;
                batch.instances.forEach(ast => ast.activating = true);
                break;
            } else if (this._placeInPlanetary !== batch.inPlanetary) {
                batch.inPlanetary = this._placeInPlanetary;
                this._batches.forEach(batch => batch.instances?.forEach(aster => aster.rectangular = this._placeInPlanetary));
                break;
            }
        }
    }

    protected lateUpdate(): void {
        if (ui.showCelNames) {
            this._claimLabels();
            for (const label of this._labels) {
                const { x, z } = label.worldOffset;
                if (this._hackHide(label)) {
                    label.alpha = 0;
                } else {
                    label.alpha = attenuate(Math.sumSqr(x, z), NameLabelAtt, true);
                }
            }
        } else this._freeLabels();
    }

    private _hackHide(label: CelestialLabel): boolean {
        return this._placeInPlanetary && (label.string === 'Main Belt' || label.string === '主小行星带');
    }

    protected onDisable(): void {
        this._freeLabels();
        this._batches.forEach(batch => {
            batch.activating = false;
            if (batch.instances) batch.instances.forEach(ast => ast.activating = false);
        });
    }

    public loadAsteroids(sun: ICelestial): void {
        this._sun = sun;
        this._configs = core.res.getConfig<Record<string, beltConfig>>(ASTEROID_BELTS);
        for (const key in this._configs) {
            const config = this._configs[key];
            const belt = createNodeAt(key, LAYER_MAIN_3D, this.node);
            this._belts.push([belt, Math.avg(...config.ranges.dist)]);
            const { num } = config;
            const batches = Math.round(num / NUM_IN_BATCH);
            for (let i = 0; i < batches; i++) {
                this._batches.push({ instances: null, activating: false, belt, config, inPlanetary: false });
            }
        }
        this.placeInSolar();
    }

    private _configs: Record<string, beltConfig>;
    private _belts: [Node, number][] = [];
    private _sun: ICelestial;
    private _placeInPlanetary: boolean = false;

    private _freeLabels(): void {
        if (notEmptyArr(this._labels)) this._labels.forEach(label => label.free());
        this._labels.length = 0;
    }

    private _claimLabels(): void {
        if (notEmptyArr(this._labels)) return;
        const camera = utils.findCamera(COSMOS, MAIN);
        this._belts.forEach(([belt, dist]) => {
            const { name, cname } = this._configs[belt.name];
            for (let i = 0; i < 3; i++) {
                const label = setLabelStyle(CelestialLabel.claim('solar', belt.name), 'solar', 'belts');;
                label.target = belt;
                label.camera = camera;
                const rad = i * Math.PI * 2 / 3;
                const [x, z] = [Math.sin(rad) * dist, Math.cos(rad) * dist];
                label.setOffset(v3a.set(x, 0, z));
                label.config = { cn: cname, en: name };
                this._labels.push(label);
            }
        });
    }

    private _labels: CelestialLabel[] = [];
    private _belong: ICelestial = null;
    private _batches: InstanceBatch[] = [];
}

const { NameLabelAtt } = UIProxy;
const { Prefabs, Configs: { ASTEROID_BELTS } } = Res;
const { COSMOS, Cameras: { MAIN } } = SceneGraph;
const v3a = new Vec3;
const NUM_IN_BATCH = 20;

declare type InstanceBatch = {
    instances: Asteroid[] | null,
    activating: boolean,
    belt: Node,
    config: beltConfig,
    inPlanetary: boolean,
}